%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Module summary
%%
%% Main authors: 
%% Progress to usefulness: 
%%
%% Stabilized chord set-up process, as described in
%% the original Chord paper. 
%% 
%%%%%%%%%%%%%%%%%%%%%%%

-module(chord_stabilized).
-include("db.hrl").
-compile(export_all).

-import(chord_lib, [dbg_str/2, dbg_format/3, dbg_header/2]).
-import(chord_lib, [dbg_msg/2, dbg_msg_loc/2]).
-import(chord_lib, [get_node/1, get_pid/1, get_position/1]).
-import(chord_lib, [get_finger/2, set_finger/3, finger_start/2, get_M/0, modspace/1]).

init_timers() ->
  {ok, SetupType} = application:get_env(db, chordsetup),
  case SetupType of
    stabilized ->
      {ok, StabilizeInterval} = application:get_env(db, stabilize_interval),
      {ok, FixFingersInterval} = application:get_env(db, fix_fingers_interval),
      timer:send_interval(StabilizeInterval, stabilize),
      timer:send_interval(FixFingersInterval, fix_fingers);
    _ -> ok
  end.

%%%%%%%%%%%%%%%%%%%%%
%%
%% Action wrappers
%%
%%%%%%%%%%%%%%%%%%%%%

%% Getters: 
%% Either performs the get on ourself, and return the value,
%% or call a remote node, and return the remote node's value. 

%% Setters: 
%% Either performs the update on ourself, and return the updated state,
%% or call a remote node, and return our old state. 

wrap_notify(State, To, N) ->
    case To#node.node == node() of
      true ->
        dbg_msg_loc("notify", State),
        notify(N, State);
      false ->
        chord_lib:rem_query_cast(To, {notify, N}, State),
        State
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Logic
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

join(N2Name, State) ->
    SelfPos = get_position(State),
    N2 = chord_lib:create_node(N2Name),
    Pred = nothing,
    Succ = chord_lib:wrap_find_successor(State, N2, SelfPos),
    State2 = set_finger(1, Succ, State),
    State3 = State2#chord{ predecessor = Pred, status = joined },
    io:format("Node ~p:~p has joined the circle~n",
              [get_node(State3), get_pid(State3)]),
    init_timers(),
    State3.

%% Set up the first node in the Chord ring. 
start_ring(State) ->
  Id = State#chord.identity,
  Ft2 = chord_lib:fill_finger_table(Id),
  State2 = State#chord{ finger_table = Ft2, predecessor = nothing, status = started },
  io:fwrite("Node ~p:~p has started the circle.~n",
            [get_node(State2), get_pid(State2)]),
  init_timers(),
  State2.

stabilize(State) ->
    case State#chord.status of
      nothing -> ok;
      _   -> stabilize_real(State)
    end.

stabilize_real(State) ->
    Self = State#chord.identity,
    SelfPos = Self#node.position,
    Succ = get_finger(1, State),
    SuccPos = Succ#node.position,
    dbg_format("Node ~p is going to stabilize~n", [Self#node.node], State),
    X = chord_lib:wrap_get_predecessor(State, Succ),
    State2 = case X of
      nothing ->
        dbg_str("Ignoring request to update our successor with an invalid nothing node~n", State),
        State;
      _ ->
        XPos = X#node.position,
        case intmath:interval_check_exc_exc(SelfPos, XPos, SuccPos) of
          true ->
            dbg_format("Updating our successor with node ~p at pos ~B~n",
                       [X#node.node, X#node.position], State),
            set_finger(1, X, State);
          false ->
            dbg_format("Ignoring request to update our successor with node ~p at pos ~B, in favor of old node ~p at position ~B~n",
                       [X#node.node, X#node.position, Succ#node.node, Succ#node.position], State),
            State
        end
    end,
    Self2 = State2#chord.identity,
    Succ2 = get_finger(1, State2),
    wrap_notify(State2, Succ2, Self2).

notify(N2, State) ->
    Self = State#chord.identity,
    SelfPos = Self#node.position,
    Pred = State#chord.predecessor,
    PredPos = case Pred of
      nothing -> nothing;
      _ -> Pred#node.position
    end,
    N2Pos = N2#node.position,
    dbg_format("Node ~p has been notified of node ~p~n", [Self#node.node, N2#node.node], State),
    case (PredPos == nothing) or (intmath:interval_check_exc_exc(PredPos, N2Pos, SelfPos)) of
      true ->
        dbg_format("Updating our predecessor with node ~p at pos ~B~n",
                   [N2#node.node, N2#node.position], State),
        State#chord{ predecessor = N2 };
      false ->
        dbg_format("Ignoring request to update our predecessor with node ~p at pos ~B, in favor of old node ~p at position ~B~n",
                   [N2#node.node, N2#node.position, Pred#node.node, Pred#node.position], State),
        State
    end.

%% FIXME: fix_fingers should only fix one finger entry chosen at random
fix_fingers(State) ->
    case State#chord.status of
      nothing -> ok;
      _ -> fix_fingers_i(get_M(), State)
    end.

fix_fingers_i(0, State) -> State;
fix_fingers_i(I, State) ->
    State2 = fix_finger(I, State),
    fix_fingers_i(I - 1, State2).

fix_finger(I, State) ->
    FingStart = finger_start(I, State),
    LookupModule = State#chord.lookup,
    dbg_format("Node ~p is going to fix finger nr ~B~n", [get_node(State), I], State),
    NewFinger = LookupModule:find_successor(FingStart, State),
    dbg_format("Node ~p is fixing finger nr ~B to node ~p~n", [get_node(State), I, NewFinger#node.node], State),
    set_finger(I, NewFinger, State).
